#pragma once
#include <limits>
#include <memory>
#include <vector>
namespace lcz
{
    class Primary_allocator
    {
    public:
        static void *allocate(size_t n)
        {
            return malloc(n);
        }
        static void deallocate(void *ptr)
        {
            free(ptr);
        }
    };
    struct ListNode
    {
        void *_mem_ptr_head;
        ListNode *_next;
    };
    class MemoryPool
    {
    public:
        enum
        {
            Max_Byte_Size = 128
        };
        enum
        {
            Align = 8
        };
        const static int vector_size = Max_Byte_Size / Align;

    public:
        static MemoryPool &getInstance()
        {
            static MemoryPool instance;
            return instance;
        }
        void *allocate(size_t byte)
        {
            if (byte > Max_Byte_Size)
                return Primary_allocator::allocate(byte);
            else
            {
                size_t index = byte / Align;
                if (_vec[index] == nullptr)
                {
                    size_t create_size = default_create_size;
                    void *ptr = operator new(Align *create_size);
                    ListNode *node = static_cast<ListNode *>(ptr);
                    node->_mem_ptr_head = ptr;
                    _vec[index] = node;
                    for (size_t i = 1; i < create_size; ++i)
                    {
                        ListNode *new_node = new (ptr + i * Align) ListNode;
                        new_node->_mem_ptr_head = ptr + i * Align;
                        new_node->_next = _vec[index];
                        _vec[index] = new_node;
                    }
                }
                ListNode *node = _vec[index];
                _vec[index] = node->_next;
                return node->_mem_ptr_head;
            }
        }

        void deallocate(void *ptr, size_t byte)
        {
            if (byte > Max_Byte_Size)
                Primary_allocator::deallocate(ptr);
            else
            {
                size_t index = byte / Align;
                ListNode *node = reinterpret_cast<ListNode *>(ptr);
                node->_mem_ptr_head = ptr;
                node->_next = _vec[index];
                _vec[index] = node;
            }
        }

    private:
        MemoryPool()
            : _vec(vector_size)
        {
        }
        MemoryPool(const MemoryPool &) = delete;
        MemoryPool &operator=(const MemoryPool &) = delete;

    private:
        const static int default_create_size = 10;
        std::vector<ListNode *> _vec;
    };

    template <typename T>
    class Allocator
    {
    public:
        using value_type = T;
        using pointer = T *;
        using const_pointer = const T *;
        using size_type = std::size_t;
        using difference_type = std::ptrdiff_t;

        template <typename U>
        struct rebind
        {
            using other = Allocator<U>;
        };

        Allocator() = default;

        template <typename U>
        Allocator(const Allocator<U> &) noexcept {}

        pointer allocate(size_type n)
        {
            if (n <= 0)
            {
                return nullptr;
            }

            size_type byte_size = n * sizeof(T);
            if (byte_size <= lcz::MemoryPool::Max_Byte_Size)
            {
                return static_cast<pointer>(lcz::MemoryPool::getInstance().allocate(byte_size));
            }
            else
            {
                return static_cast<pointer>(lcz::Primary_allocator::allocate(byte_size));
            }
        }

        void deallocate(pointer ptr, size_type n)
        {
            size_type byte_size = n * sizeof(T);
            if (byte_size <= lcz::MemoryPool::Max_Byte_Size)
            {
                lcz::MemoryPool::getInstance().deallocate(ptr, byte_size);
            }
            else
            {
                lcz::Primary_allocator::deallocate(ptr);
            }
        }

        size_type max_size() const noexcept
        {
            return std::numeric_limits<size_type>::max() / sizeof(T);
        }

        void construct(pointer p, const T &value)
        {
            new (p) T(value);
        }

        void destroy(pointer p)
        {
            p->~T();
        }

        template <typename U>
        bool operator==(const Allocator<U> &) const noexcept
        {
            return true;
        }

        template <typename U>
        bool operator!=(const Allocator<U> &other) const noexcept
        {
            return !operator==(other);
        }
    };
}